#pragma once
#include <map>
#include <list>
#include <chrono>
#include <mutex>
#include <optional>

namespace cpp
{
	template<class K, class V, template<class...> class Map = std::map, class Mutex = std::mutex >
	class cache
		:private Mutex
	{
		typedef K key_type;
		typedef V value_type;

		struct Node
		{
			key_type	key;
			value_type	value;
			std::chrono::steady_clock::time_point tp;

			template<class U>
			Node(key_type const& k, U&& v)
				: key(k)
				, value(std::forward<U>(v))
				, tp(std::chrono::steady_clock::now())
			{}
		};

		typedef Node node_type;
		typedef typename std::list<node_type>::iterator		list_iterator_t;

		typedef std::chrono::steady_clock::duration duration;
	public:
		cache(std::size_t max_size, duration const& cache_ttl = (duration::max)())
			: _max_size(max_size)
			, _cache_ttl(cache_ttl)
		{

		}

		std::optional<value_type> get(const key_type& key)
		{
			std::lock_guard<Mutex> locker(_mutex());

			auto it = _map.find(key);
			if(it == _map.end())
			{
				return std::nullopt;
			}
			else
			{
				auto now = std::chrono::steady_clock::now();
				if(now - it->second->tp > _cache_ttl)
				{
					_list.erase(it->second);
					_map.erase(it);
					return std::nullopt;
				}

				_list.splice(_list.begin(), _list, it->second);
				return it->second->value;
			}
		}

		template<class U>
		void set(const key_type& key, U&& value)
		{
			static_assert(std::is_constructible_v<value_type, U> && std::is_assignable_v<value_type&, U>);

			std::lock_guard<Mutex> locker(_mutex());

			auto [iter, inserted] = _map.try_emplace(key, list_iterator_t{});

			if(inserted)
			{
				_list.push_front(node_type(key, std::forward<U>(value)));
				iter->second = _list.begin();

				if(_map.size() > _max_size)
				{
					_map.erase(_list.back().key);
					_list.pop_back();
				}
			}
			else
			{
				iter->second->value = std::forward<U>(value);
				iter->second->tp = std::chrono::steady_clock::now();
				_list.splice(_list.begin(), _list, iter->second);
			}
		}

		void erase(const key_type& key)
		{
			std::lock_guard<Mutex> locker(_mutex());

			auto it = _map.find(key);
			if(it != _map.end())
			{
				_list.erase(it->second);
				_map.erase(it);
			}
		}

		// don't check key TTL
		bool contains(const key_type& key) const
		{
			std::lock_guard<Mutex> locker(_mutex());
			auto it = _map.find(key);
			return (it != _map.end());
		}

		std::size_t size() const
		{
			std::lock_guard<Mutex> locker(_mutex());
			return _map.size();
		}

	protected:
		Mutex& _mutex() const
		{
			return const_cast<cache&>(*this);
		}
	private:
		Map<key_type, list_iterator_t>	_map;
		std::list<node_type>			_list;
		std::size_t						_max_size;
		duration						_cache_ttl;
	};

}